
open Ast
open Ast_fullfill
open Ast_absolute

type variable = {
	var_value : assignmentExpression option;
	var_n_reads : int;
	var_absolute : StrSet.t;
	var_value_sure : bool;
}

type env = {
	env_ident : string;
	env_sure : bool;
	env_in_function : bool
}

exception Logic_error

let rec semitest_list func var env = function
	| [] -> var, []
	| a::b -> let var, value = func var env a in
		if var.var_n_reads < 2 then let var, lst = semitest_list func var env b in var, value::lst else var,[value]

let rec remove_unused_program var env ast = 
	semitest_list remove_unused_sourceElement var env ast

and make_unsure env =
	{
		env_ident = env.env_ident;
		env_sure = false;
		env_in_function = env.env_in_function
	} 

and remove_unused_sourceElement var env = function
	| AStatement stmt -> 
		let var, stmt = remove_unused_statement var env stmt in
		var, (AStatement stmt)
	(*| AFunctionDeclaration funcDec -> 
		let var, stmt = remove_unused_functionDeclaration var env funcDec in 
		var, (AFunctionDeclaration funcDec)*) (* Modif #1 *)

and remove_unused_statement var env = function
	| ABlock block -> 
		let var, block = remove_unused_block var env block in
		var, (ABlock block)
	| AVariableStatement vs ->
		let var, vs = remove_unused_variableStatement var env vs in
		var, (match vs with
			| None -> AEmptyStatement
			| Some vs -> AVariableStatement vs)
	| AConstStatement cs ->
		let var, cs = remove_unused_variableStatement var env cs in
		var, (match cs with
			| None -> AEmptyStatement
			| Some cs -> AConstStatement cs)
	| AFuncDeclaration funcDecl ->
		let var, funcDecl = remove_unused_functionDeclaration var env funcDecl in
		var, (match funcDecl with
			| None -> AEmptyStatement
			| Some funcDecl -> AFuncDeclaration funcDecl)	(* Modif #1 *)
	| AEmptyStatement -> var, AEmptyStatement
	| AExpressionStatement stmt -> 
		let var, stmt = remove_unused_expression var env stmt in
		var, (AExpressionStatement stmt)
	| AIfStatement stmt -> 
		let var, stmt = remove_unused_ifStatement var env stmt in
		var, (AIfStatement stmt)
	| AIterationStatement stmt -> 
		let var, stmt = remove_unused_iterationStatement var env stmt in
		var, (AIterationStatement stmt)
	| AContinueStatement stmt -> var, (AContinueStatement stmt)
	| ABreakStatement stmt -> var, (ABreakStatement stmt)
	| AReturnStatement stmt -> 
		let var, stmt = remove_unused_returnStatement var env stmt in
		var, (AReturnStatement stmt)
	| AWithStatement stmt -> 
		let var, stmt = remove_unused_withStatement var env stmt in
		var, (AWithStatement stmt)
	| ALabelledStatement stmt -> 
		let var, stmt = remove_unused_labelledStatement var env stmt in
		var, (ALabelledStatement stmt)
	| ASwitchStatement stmt -> 
		let var, stmt = remove_unused_switchStatement var env stmt in
		var, (ASwitchStatement stmt)
	| AThrowStatement expr -> 
		let var, expr = remove_unused_expression var env expr in
		var, (AThrowStatement expr)
	| ATryStatement stmt -> 
		let var, stmt = remove_unused_tryStatement var env stmt in
		var, (ATryStatement stmt)
	| ADebuggerStatement -> var, ADebuggerStatement

	and remove_unused_block var env block =
		semitest_list remove_unused_statement var env block

	and remove_unused_variableStatement var env (b,c,varDecls) =
		let var, varDecls = semitest_list remove_unused_variableDeclaration var env varDecls in
		let varDecls = List.fold_right (fun x lst-> match x with
			| None -> lst
			| Some x -> x::lst) varDecls [] in
		if varDecls = [] then var,None else var,Some (b,c,varDecls)
	
	and remove_unused_variableDeclaration var env (ident,expr) =
		if ident = env.env_ident then
			(let idents = (match expr with
				| None -> StrSet.empty
				| Some expr -> let absolute = absolute_assignmentExpression {
					abs_idents = StrSet.empty;
					abs_labels = StrSet.empty
				} StrSet.empty expr in absolute.abs_idents
			) in
			let var = {
				var_value = if expr = None then var.var_value else expr;
				var_n_reads = if var.var_n_reads > 1 || expr = None || not env.env_sure then var.var_n_reads else 0;
				var_absolute = idents;
				var_value_sure = if expr = None then var.var_value_sure else env.env_sure;
			} in
			var, None)
		else
			let var = {
				var_value = var.var_value;
				var_n_reads = var.var_n_reads;
				var_absolute = var.var_absolute;
				var_value_sure = var.var_value_sure && ((expr = None) || not (StrSet.mem ident var.var_absolute));
			} in
			(match expr with
				| None -> var, Some (ident,None)
				| Some expr -> let var, expr = remove_unused_assignmentExpression var env expr in
					var, Some (ident,Some expr)
			)
	
	and remove_unused_ifStatement var env = function
		| AIfElse (expr, stmt1, stmt2) -> 
			let var, expr = remove_unused_expression var env expr in
			let env = make_unsure env in
			let var, stmt1 = remove_unused_statement var env stmt1 in
			let var, stmt2 = remove_unused_statement var env stmt2 in
			var, AIfElse (expr, stmt1, stmt2 )
		| AIf (expr, stmt) ->
			let var, expr = remove_unused_expression var env expr in
			let env = make_unsure env in
			let var, stmt = remove_unused_statement var env stmt in
			var, AIf (expr, stmt)
	
	and remove_unused_iterationStatement var env = function
		| ADoWhile (stmt, expr) ->
			let env2 = make_unsure env in
			let var, stmt = remove_unused_statement var env2 stmt in 
			let var, expr = remove_unused_expression var env expr in
			var, ADoWhile (stmt, expr)
		| AWhile (expr, stmt) ->
			let var, expr = remove_unused_expression var env expr in
			let env = make_unsure env in
			let var, stmt = remove_unused_statement var env stmt in 
			var, AWhile (expr, stmt)
		| AFor (expr1, expr2, expr3, stmt ) ->
			let var, expr1 = remove_unused_optionalExpression var env expr1 in
			let env = make_unsure env in
			let var, expr2 = remove_unused_optionalExpression var env expr2 in
			let var, expr3 = remove_unused_optionalExpression var env expr3 in
			let var, stmt = remove_unused_statement var env stmt in 
			var, AFor (expr1, expr2, expr3, stmt)
		| AForVar (is_meta, varDecls, expr1, expr2, stmt) ->
			let var, varDecls = semitest_list 
				(fun var env x -> let var, x = remove_unused_variableDeclaration var env x in
					match x with
						| None -> raise Logic_error
						| Some x -> var, x
				) var env varDecls in
			let var, expr1 = remove_unused_optionalExpression var env expr1 in
			let env = make_unsure env in
			let var, expr2 = remove_unused_optionalExpression var env expr2 in
			let var, stmt = remove_unused_statement var env stmt in 
			var, AForVar (is_meta, varDecls, expr1, expr2, stmt)
		| AForIn (lhs, expr, stmt) ->
			let var, lhs = remove_unused_leftHandSideExpression var env lhs in
			let env = make_unsure env in
			let var, expr = remove_unused_expression var env expr in
			let var, stmt = remove_unused_statement var env stmt in 
			var, AForIn (lhs, expr, stmt)
		| AForVarIn (is_meta, varDecl, expr, stmt) ->
			let var, varDecl = remove_unused_variableDeclaration var env varDecl in
			let varDecl = (match varDecl with
				| Some varDecl -> varDecl
				| None -> raise Logic_error
			) in
			let env = make_unsure env in
			let var, expr = remove_unused_expression var env expr in
			let var, stmt = remove_unused_statement var env stmt in
			var, AForVarIn (is_meta, varDecl, expr, stmt)
			
	and remove_unused_optionalExpression var env = function
		| None -> var, None
		| Some expr -> let var, expr = remove_unused_expression var env expr in
			var, (Some expr)
	
	and remove_unused_returnStatement var env expr =
		remove_unused_optionalExpression var env expr
	
	and remove_unused_withStatement var env (expr,stmt) =
		let var, expr = remove_unused_expression var env expr in
		let env = make_unsure env in
		let var, stmt = remove_unused_statement var env stmt in
		var, (expr, stmt)
	
	and remove_unused_labelledStatement var env (ident,stmt) =
		let var, stmt = remove_unused_statement var env stmt in
		var, (ident, stmt)
	
	and remove_unused_switchStatement var env (expr,caseBlock) =
		let var, expr = remove_unused_expression var env expr in
		let env = make_unsure env in
		let var, caseBlock = remove_unused_caseBlock var env caseBlock in
		var, (expr, caseBlock)
	
	and remove_unused_caseBlock var env = function
		| ACasesNoDefault caseClauses ->
			let var, caseClauses = semitest_list remove_unused_caseClause var env caseClauses in
			var, ACasesNoDefault caseClauses
		| ACasesDefault (caseClauses1, defaultClause, caseClauses2) ->
			let var, caseClauses1 = semitest_list remove_unused_caseClause var env caseClauses1 in
			let var, defaultClause = semitest_list remove_unused_statement var env defaultClause in
			let var, caseClauses2 = semitest_list remove_unused_caseClause var env caseClauses2 in
			var, ACasesDefault (caseClauses1, defaultClause, caseClauses2)
	
	and remove_unused_caseClause var env (expr, stmts) =
		let var, expr = remove_unused_expression var env expr in
		let var, stmts = semitest_list remove_unused_statement var env stmts in
		var, (expr, stmts)
	
	and remove_unused_tryStatement var env = function
		| ATryCatch (block, catch) ->
			let env = make_unsure env in
			let var, block = remove_unused_block var env block in
			let var, catch = remove_unused_catch var env catch in
			var, ATryCatch (block, catch)
		| ATryFinally (block, finally) ->
			let env2 = make_unsure env in
			let var, block = remove_unused_block var env2 block in
			let var, finally = remove_unused_block var env finally in
			var, ATryFinally (block, finally)
		| ATryCatchFinally (block, catch, finally) ->
			let env2 = make_unsure env in
			let var, block = remove_unused_block var env2 block in
			let var, catch = remove_unused_catch var env2 catch in
			let var, finally = remove_unused_block var env finally in
			var, ATryCatchFinally (block, catch, finally)
	
	and remove_unused_catch var env (ident,stmts) =
		if ident = env.env_ident then
			var, (ident,stmts)
		else let var, stmts = semitest_list remove_unused_statement var env stmts in
			var, (ident,stmts)
	
	and remove_unused_expression var env exprs =
		semitest_list remove_unused_assignmentExpression var env exprs
	
	and remove_unused_assignmentExpression var env = function
		| ACondExpr expr -> 
			let var, expr = remove_unused_conditionalExpression var env expr in
			var, ACondExpr expr
		| AAssignOp (lhs, op, expr) ->
			if lhs = 
				ANewExpression(AMemberExpression(APrimaryExpr(AIdentifier(env.env_ident))))
			then
				(*let var2 = var in*)
				let var, expr = remove_unused_assignmentExpression var env expr in
				let absolute = absolute_assignmentExpression {
					abs_idents = StrSet.empty;
					abs_labels = StrSet.empty
				} StrSet.empty expr in
				let var = {
					var_value = if env.env_sure then Some expr else None;
					var_n_reads = if var.var_n_reads > 1 || op <> "=" (*|| var.var_n_reads > var2.var_n_reads*) then 2 else 0;
					var_absolute = absolute.abs_idents;
					var_value_sure = env.env_sure;
				} in
				var, expr
			else 
				let var, expr = remove_unused_assignmentExpression var env expr in
				let var = (match lhs with
					| ANewExpression(AMemberExpression(APrimaryExpr(AIdentifier(ident)))) ->
						{
							var_value = var.var_value;
							var_n_reads = var.var_n_reads;
							var_absolute = var.var_absolute;
							var_value_sure = var.var_value_sure && not (StrSet.mem ident var.var_absolute);
						}
					| _ -> var) in
				let var, lhs = remove_unused_leftHandSideExpression var env lhs in
				var, AAssignOp (lhs, op, expr)
	
	and remove_unused_leftHandSideExpression var env = function
		| ANewExpression expr -> 
			let var, expr = remove_unused_newExpression var env expr in
			var, ANewExpression expr
		| ACallExpression expr ->
			let var, expr = remove_unused_callExpression var env expr in
			var, ACallExpression expr
	
	and remove_unused_newExpression var env = function
		| AMemberExpression expr -> 
			let var, expr = remove_unused_memberExpression var env expr in
			var, AMemberExpression expr
		| ANewMemberExpression expr ->
			let var, expr = remove_unused_newExpression var env expr in
			var, ANewMemberExpression expr
			
	and remove_unused_arguments var env args =
		semitest_list remove_unused_assignmentExpression var env args
	
	and remove_unused_callExpression var env = function
		| ACallMemberArgs (expr, args) -> 
			let var, expr = remove_unused_memberExpression var env expr in
			let var, args = remove_unused_arguments var env args in
			var, ACallMemberArgs (expr, args)
		| ACallExpressionArgs (expr, args) ->
			let var, expr = remove_unused_callExpression var env expr in 
			let var, args = remove_unused_arguments var env args in
			var, ACallExpressionArgs (expr, args)
		| ACallExpressionExpr (expr1, expr2) ->
			let var, expr1 = remove_unused_callExpression var env expr1 in
			let var, expr2 = remove_unused_expression var env expr2 in
			var, ACallExpressionExpr (expr1, expr2)
		| ACallExpressionIdent (expr,ident) ->
			let var, expr = remove_unused_callExpression var env expr in
			var, ACallExpressionIdent (expr, ident)
	
	and remove_unused_memberExpression var env tmp = match tmp with
		| AMemberDotExpr (APrimaryExpr(AThis), e)
		| AMemberDotExpr (APrimaryExpr(AIdentifier ("window")), e) ->
			if e = env.env_ident && not env.env_in_function then
				let has_sideEffects = (match var.var_value with
					| None -> false
					| Some expr -> Simplifier_sideEffects.sideEffects_assignmentExpression true expr
					) in
				let var = {
					var_value = var.var_value;
					var_n_reads = if (var.var_n_reads == 0 && has_sideEffects) || not var.var_value_sure then 2 else var.var_n_reads + 1;
					var_absolute = var.var_absolute;
					var_value_sure = var.var_value_sure
				} in
				var, (match var.var_value with
					| None -> APrimaryExpr (AIdentifier "undefined")
					| Some expr -> (match expr with
						| ACondExpr(ACondBinaryExpr(AUnaryExpr(APostfixExpr(ALeftHandSide(ANewExpression(AMemberExpression(APrimaryExpr(expr)))))))) -> 
							APrimaryExpr expr
						| _ -> APrimaryExpr (AExpression [expr]))
				)
			else
				var, tmp
		
		
		| AMemberDotExpr (_, e) when e = env.env_ident && not env.env_in_function ->
			let var = {
				var_value = var.var_value;
				var_n_reads = 2;
				var_absolute = var.var_absolute;
				var_value_sure = var.var_value_sure
			} in
			var, tmp
			
		| APrimaryExpr expr -> 
			let var, expr = remove_unused_primaryExpression var env expr in
			var, APrimaryExpr expr
		| AFunctionExpr expr ->
			let var, expr = remove_unused_functionExpression var env expr in
			var, AFunctionExpr expr
		| AMemberBracketExpr (expr1, expr2) ->
			let var, expr1 = remove_unused_memberExpression var env expr1 in
			let var, expr2 = remove_unused_expression var env expr2 in
			var, AMemberBracketExpr (expr1, expr2)
		| AMemberDotExpr (expr, ident) -> 
			let var, expr = remove_unused_memberExpression var env expr in
			var, AMemberDotExpr (expr,ident)
		| AMemberNewExpr (expr, args) -> 
			let var, expr = remove_unused_memberExpression var env expr in
			let var, args = remove_unused_arguments var env args in
			var, AMemberNewExpr (expr, args)

	and remove_unused_primaryExpression var env = function
		| AThis -> var, AThis
		| AIdentifier ident -> 
			if env.env_ident = ident then
				let has_sideEffects = (match var.var_value with
					| None -> false
					| Some expr -> Simplifier_sideEffects.sideEffects_assignmentExpression true expr
					) in
				let var = {
					var_value = var.var_value;
					var_n_reads = if (var.var_n_reads == 0 && has_sideEffects) || not var.var_value_sure then 2 else var.var_n_reads + 1;
					var_absolute = var.var_absolute;
					var_value_sure = var.var_value_sure
				} in
				var, (match var.var_value with
					| None -> AIdentifier "undefined"
					| Some expr -> (match expr with
						| ACondExpr(ACondBinaryExpr(AUnaryExpr(APostfixExpr(ALeftHandSide(ANewExpression(AMemberExpression(APrimaryExpr(expr)))))))) -> expr
						| _ -> AExpression [expr])
				)
			else var, AIdentifier ident
		| ALiteral literal -> var, ALiteral literal
		| AArrayLiteral literal ->
			let var, literal = remove_unused_arrayLiteral var env literal in
			var, AArrayLiteral literal
		| AObjectLiteral literal -> 
			let var, literal = remove_unused_objectLiteral var env literal in
			var, AObjectLiteral literal
		| AExpression expr ->
			let var, expr = remove_unused_expression var env expr in
			var, AExpression expr
	
	and remove_unused_arrayLiteral var env = function
		| AElisions elisions -> var, AElisions elisions
		| AElements elements -> 
			let var, elements = semitest_list remove_unused_element var env elements in
			var, AElements elements
		| AElementsAndElisions (elements,elisions) ->
			let var, elements = semitest_list remove_unused_element var env elements in
			var, AElementsAndElisions (elements, elisions)
	
	and remove_unused_element var env (elisions,expr) =
		let var, expr = remove_unused_assignmentExpression var env expr in
		var, (elisions,expr)
	
	and remove_unused_objectLiteral var env props =
		semitest_list remove_unused_propertyAssignment var env props
	
	and remove_unused_propertyAssignment var env = function
		| APropAssignment (name, expr) ->
			let var, expr = remove_unused_assignmentExpression var env expr in
			var, APropAssignment(name,expr)
		| APropGet (name, body) ->
			let var, _, body = remove_unused_functionContent var env [] body in
			var, APropGet(name, body)
		| APropSet (name, params, body) ->
			 let var, params, body = remove_unused_functionContent var env params body in
			var, APropSet(name, params,body)
	
	and remove_unused_binaryExpression var env = function
		| AUnaryExpr expr ->
			let var, expr = remove_unused_unaryExpression var env expr in
			var, AUnaryExpr expr
		| ABinaryExpr (op, expr1, expr2) ->
			let var, expr1 = remove_unused_binaryExpression var env expr1 in
			let env = make_unsure env in
			let var, expr2 = remove_unused_binaryExpression var env expr2 in
			var, ABinaryExpr (op, expr1, expr2)
	
	and remove_unused_unaryExpression var env = function
		| APostfixExpr expr -> 
			let var, expr = remove_unused_postfixExpression var env expr in
			var, APostfixExpr expr
		| AOpUnaryExpr (op, expr) ->
			let var, expr = remove_unused_unaryExpression var env expr in
			let var = {
				var_value = var.var_value;
				var_n_reads = if String.length op > 1 then 2 else var.var_n_reads;
				var_absolute = var.var_absolute;
				var_value_sure = var.var_value_sure
			} in
			var, AOpUnaryExpr (op, expr)
	
	and remove_unused_postfixExpression var env = function
		| ALeftHandSide lhs -> 
			let var, lhs = remove_unused_leftHandSideExpression var env lhs in
			var, ALeftHandSide lhs
		| ALeftHandSideOp (lhs,op) ->
			if lhs = ANewExpression(AMemberExpression(APrimaryExpr(AIdentifier(env.env_ident)))) then
				let var = {
					var_value = var.var_value;
					var_n_reads = 2;
					var_absolute = var.var_absolute;
					var_value_sure = var.var_value_sure
				} in
				var, ALeftHandSideOp (lhs, op)
			else
				let var, lhs = remove_unused_leftHandSideExpression var env lhs in
				var, ALeftHandSideOp (lhs,op)
	
	and remove_unused_conditionalExpression var env = function
		| ACondBinaryExpr expr ->
			let var, expr = remove_unused_binaryExpression var env expr in
			var, ACondBinaryExpr expr
		| ACondTernaryExpr (expr1, expr2, expr3) ->
			let var, expr1 = remove_unused_binaryExpression var env expr1 in
			let env = make_unsure env in
			let var, expr2 = remove_unused_assignmentExpression var env expr2 in
			let var, expr3 = remove_unused_assignmentExpression var env expr3 in
			var, ACondTernaryExpr (expr1, expr2, expr3)
	
	and remove_unused_functionExpression var env (inline,ident,params,body) =
		let var, params, body = remove_unused_functionContent var env params body in
		var, (inline,ident,params,body)
		
	and remove_unused_functionDeclaration var env (is_local, is_meta, is_inline,ident, params,body) =
		if ident = env.env_ident then
			let var = {
				var_value = None;
				var_n_reads = 0;
				var_absolute = StrSet.empty;
				var_value_sure = false
			} in
			var, None
		else
		let var, params, body = remove_unused_functionContent var env params body in
		var, Some (is_local, is_meta, is_inline, ident,params,body)

	and remove_unused_functionContent var env params body =
		if List.mem env.env_ident params then
		var, params, body
		else
			let fullfill = fullfill_list fullfill_sourceElement {
				idents = StrSet.empty;
				labels = StrSet.empty;
				locals = StrSet.empty
			} body in
			let env = {
					env_ident = env.env_ident;
					env_sure = env.env_sure;
					env_in_function = true
			} in
			if StrSet.mem env.env_ident fullfill.idents then
				var, params, body
			else
			let absolute = absolute_list absolute_sourceElement {
				abs_idents = StrSet.empty;
				abs_labels = StrSet.empty
				} StrSet.empty body in
			(if StrSet.mem env.env_ident absolute.abs_idents then
			{
				var_value = None;
				var_n_reads = 2;
				var_absolute = StrSet.empty;
				var_value_sure = false
			}, params, body
			else var, params, body)
	
